﻿using System;
using System.IO;
using System.Threading;
using System.Collections.Generic;
using System.Web;
using System.Web.Mvc;
using System.Web.Routing;
using System.Web.Hosting;
using System.Web.Configuration;
using System.Text.RegularExpressions;

using Fuse8.DomainFramework.Common;
using Fuse8.DomainFramework.Common.Logging;

namespace Fuse8.ContentManagementFramework.Web.Caching
{
    /// <summary>
    /// Singleton
    /// </summary>
    public class CmsOutputCacheManager : IRegisteredObject
    {
        #region Fields

        private static object _syncRoot = new object();
        private static CmsOutputCacheManager _instance;

        private CmsOutputCacheConfiguration _configuration;
        private Dictionary<string, CmsOutputCacheTracker> _trackers =
            new Dictionary<string, CmsOutputCacheTracker>();

        private Timer _scavangingTimer;
        private LinkedList<CmsOutputCacheScavangerItem> _fileRemovalList = new LinkedList<CmsOutputCacheScavangerItem>(); 

        #endregion

        #region Constructor

        static CmsOutputCacheManager()
        {
            lock (_syncRoot)
            {
                if (_instance == null)
                {
                    _instance = new CmsOutputCacheManager();

                    _instance.StartUp();

                    HostingEnvironment.RegisterObject(_instance);
                }
            }
        } 

        #endregion

        #region IRegisteredObject Members

        public void Stop(bool immediate)
        {
            if (!immediate)
            {
                Cleanup();
            }
            
            HostingEnvironment.UnregisterObject(this);
        }        

        #endregion

        #region Public static methods

        public static CmsOutputCacheTracker Lookup(HttpContextBase context)
        {
            return CanUseCacheTracker(context) ?
                _instance.LookupTracker(context) :
                null;
        }        

        public static void ScheduleFileDeletion(string filename)
        {
            _instance.AddToRemovalList(filename);

            _instance.ScheduleScavanger();
        } 

        #endregion

        #region Private methods

        private static bool CanUseCacheTracker(HttpContextBase context)
        {            
            return context.Request.HttpMethod.Equals("GET", StringComparison.InvariantCultureIgnoreCase);
        }
        
        private void StartUp()
        {
            _configuration = WebConfigurationManager.GetWebApplicationSection("fileOutputCache") as CmsOutputCacheConfiguration;

            Guard.ArgumentNotNull(_configuration, "fileOutputCache");
            
            if (!Directory.Exists(_configuration.CacheDirectoryLocation))
            {
                Directory.CreateDirectory(_configuration.CacheDirectoryLocation);
            }
        }

        private void Cleanup()
        {
            if (_scavangingTimer != null)
            {
                _scavangingTimer.Dispose();
            }
        }

        private CmsOutputCacheTracker LookupTracker(HttpContextBase context)
        {
            Guard.ArgumentNotNull(context, "context");
            
            CmsOutputCacheTracker result = null;

            var request = context.Request;

            lock (_syncRoot)
            {
                string path = request.Path;

                if (!_trackers.TryGetValue(path, out result))
                {
                    bool canTrack = true;
                    bool serveFromMemory = false;

                    foreach (CachedUrlsElement denyCachedUrl in _configuration.DenyCachedUrls)
                    {
                        if (denyCachedUrl.Path.Contains("*"))
                        {
                            Regex regex = new Regex(denyCachedUrl.Path.Replace(@"*", @".*"));

                            if (regex.IsMatch(path))
                            {
                                canTrack = false;
                                break;
                            }
                        }
                        else if (path.Equals(denyCachedUrl.Path, StringComparison.CurrentCultureIgnoreCase))
                        {
                            canTrack = false;
                            break;
                        }
                    }

                    if (canTrack)
                    {
                        foreach (CachedUrlsElement allowCachedUrl in _configuration.AllowCachedUrls)
                        {
                            if (path.Equals(allowCachedUrl.Path, StringComparison.CurrentCultureIgnoreCase))
                            {
                                serveFromMemory = allowCachedUrl.ServeFromMemory;
                                break;
                            }
                        }

                        result = new CmsOutputCacheTracker(
                            path,
                            context.Request.Headers.Get("Accept-Encoding"),
                            _configuration.CacheDirectoryLocation,
                            serveFromMemory,
                            _configuration.CapturingWaitTime);

                        _trackers.Add(request.Path, result);
                    }
                }
            }

            return result;
        }        

        private void AddToRemovalList(string filename)
        {
            Guard.ArgumentNotEmpty(filename, "filename");
            
            var item = new CmsOutputCacheScavangerItem
            {
                Filename = filename,
                DateTimeToDelete = DateTime.UtcNow.Add(_configuration.FileRemovalDelay)
            };

            lock (_fileRemovalList)
            {
                _fileRemovalList.AddLast(item);
            }
        }

        private void ScheduleScavanger()
        {
            if (_scavangingTimer == null)
            {
                lock (_syncRoot)
                {
                    if (_scavangingTimer == null)
                    {
                        _scavangingTimer = new Timer(ScavangerCallback, null, 
                            checked((int)_configuration.FileRemovalDelay.TotalMilliseconds), Timeout.Infinite);
                    }
                }
            }
        }

        private void ScavangerCallback(object state)
        {
            int countDeleted = 0;
            int countSkipped = 0;

            try
            {
                DateTime utcNow = DateTime.UtcNow;

                var filesForDeleting = new List<string>();

                lock (_fileRemovalList)
                {
                    var next = _fileRemovalList.First;

                    while (next != null)
                    {
                        var current = next;

                        next = current.Next;

                        if (utcNow > current.Value.DateTimeToDelete)
                        {
                            filesForDeleting.Add(current.Value.Filename);

                            _fileRemovalList.Remove(current);

                            countDeleted++;
                        }
                        else
                        {
                            countSkipped++;
                        }
                    }
                }

                foreach (string filename in filesForDeleting)
                {
                    try
                    {
                        File.Delete(filename);
                    }
                    catch(Exception ex)
                    {
                        LoggingFacade.LogError(ex.Message, ex);
                    }
                }
            }
            finally
            {
                _scavangingTimer = null;
            }

            if (countSkipped > 0)
            {
                ScheduleScavanger();
            }
        }

        #endregion
    }
}
